﻿namespace Blaze.Framework.Loaders
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using Logging;
    using UnityEngine;

    /// <summary>
    /// 可重用的资源加载器的池实现。
    /// </summary>
    [Singleton]
    public class AssetLoaderPool : MonoBehaviour
    {
        private class AssetLoaderCache : Dictionary<string, AssetLoaderBase>
        {
        }

        /// <summary>
        /// 获取或设置自动清理的时间间隔。
        /// </summary>
        public static float CleanupInterval { get; set; } = 10f;

        /// <summary>
        /// 清理所有待卸载的加载器。
        /// </summary>
        public static void CleanupAll()
        {
            var maxTimes = 10;
            var time = 0;
            while (mPendingDisposeLoaders.Count > 0 && time++ < maxTimes)
                cleanup();
            if (mPendingDisposeLoaders.Count > 0)
                mLog.Warn("[AssetLoaderPool]清理资源异常");
        }

        /// <summary>
        /// 创建指定类型的资源加载器。
        /// </summary>
        /// <param name="type">加载器类型</param>
        /// <param name="uri">资源定位标识符</param>
        public static AssetLoaderBase Create(Type type, string uri)
        {
            var ret = (AssetLoaderBase) Activator.CreateInstance(type);
            ret.CreateFromPool(uri);
            if (Application.isEditor)
                AssetLoaderTracer.Trace(ret);
            return ret;
        }

        /// <summary>
        /// 创建指定类型的资源加载器。
        /// </summary>
        /// <param name="uri">资源定位标识符</param>
        public static T Create<T>(string uri) where T : AssetLoaderBase, new()
        {
            return (T) Create(typeof(T), uri);
        }

        /// <summary>
        /// 从池中获取一个指定类型的资源加载器，若不存在则创建一个新的。
        /// </summary>
        /// <param name="type">资源加载器类型</param>
        /// <param name="uri">资源定位标识符</param>
        public static AssetLoaderBase Get(Type type, string uri)
        {
            if (type == null)
                throw new ArgumentNullException(nameof(type));
            if (uri == null)
                throw new ArgumentNullException(uri);
            var uriText = uri;
            var cache = getCache(type);
            if (cache.TryGetValue(uriText, out var ret))
            {
                if (mPendingDisposeLoaders.Remove(ret))
                    ret.Resurrect();
                ret.GetFromPool();
                return ret;
            }
            ret = (AssetLoaderBase) Activator.CreateInstance(type);
            ret.CreateFromPool(uri);
            cache[uriText] = ret;
            if (Application.isEditor)
                AssetLoaderTracer.Trace(ret);
            return ret;
        }

        /// <summary>
        /// 从池中获取一个指定类型的资源加载器，若不存在则创建一个新的。
        /// </summary>
        /// <typeparam name="T">资源加载器类型</typeparam>
        public static T Get<T>(string uri) where T : AssetLoaderBase, new()
        {
            return (T) Get(typeof(T), uri);
        }

        /// <summary>
        /// 初始化管理器。
        /// </summary>
        [RuntimeInitializeOnLoadMethod]
        public static void Initialize()
        {
            if (mIsInitialized)
                return;
            mInstance = Singleton.Get<AssetLoaderPool>();
            mIsInitialized = true;
        }

        /// <summary>
        /// 将一个资源加载器放回池中。
        /// </summary>
        public static void Put(AssetLoaderBase loader)
        {
            if (BlazeEngine.IsShuttingDown)
                return;

            loader.ReturnToPool();
            if (loader.IsReadyForDisposal)
                mPendingDisposeLoaders.Add(loader);
        }

        private static void cleanup()
        {
            mCleanupStopwatch.Reset();
            mCleanupStopwatch.Start();

            mDisposingLoaders.Clear();
            foreach (var loader in mPendingDisposeLoaders)
                mDisposingLoaders.Add(loader);
            mPendingDisposeLoaders.Clear();

            foreach (var loader in mDisposingLoaders)
            {
                removeFromCache(loader);
                loader.Dispose();
            }

            if (mCleanupStopwatch.ElapsedMilliseconds > 1000 / 60f)
                mLog.WarnFormat("[AssetLoaderPool]清理消耗时间过长 -> {0}ms", mCleanupStopwatch.ElapsedMilliseconds);
            mCleanupStopwatch.Stop();
        }

        private static AssetLoaderCache getCache(Type type)
        {
            if (mCachedLoaders.TryGetValue(type, out var ret))
                return ret;
            ret = new AssetLoaderCache();
            mCachedLoaders[type] = ret;
            return ret;
        }

        private static bool removeFromCache(AssetLoaderBase loader)
        {
            if (loader == null)
                return false;
            var loaderType = loader.GetType();
            if (mCachedLoaders.TryGetValue(loaderType, out var cache))
                return cache.Remove(loader.Uri);
            return false;
        }

        private void Update()
        {
            if (mLastCleanupTime < 0 || Time.time - mLastCleanupTime >= CleanupInterval)
            {
                cleanup();
                mLastCleanupTime = Time.time;
            }
        }

        private static readonly Dictionary<Type, AssetLoaderCache> mCachedLoaders = new Dictionary<Type, AssetLoaderCache>();
        private static readonly Stopwatch mCleanupStopwatch = new Stopwatch();
        private static readonly List<AssetLoaderBase> mDisposingLoaders = new List<AssetLoaderBase>();
        private static AssetLoaderPool mInstance;
        private static bool mIsInitialized;
        private static float mLastCleanupTime = -1;
        private static readonly Log mLog = LogManager.GetLogger(typeof(AssetLoaderPool));
        private static readonly HashSet<AssetLoaderBase> mPendingDisposeLoaders = new HashSet<AssetLoaderBase>();
    }
}